package com.googlecode.curvedmetromaps.core.presentation;

import playn.core.Canvas;
import playn.core.Path;

import com.googlecode.curvedmetromaps.core.model.MetroEdge;
import com.googlecode.curvedmetromaps.core.model.MetroLine;
import com.googlecode.curvedmetromaps.core.model.MetroMap;
import com.googlecode.curvedmetromaps.core.model.MetroStation;

public class Renderer {
	public final int	stationColor	= 0xFF000000;
	public final int	edgeColor		= 0xFF000000;
	public final float	stationRadius	= 3;

	/** Variables to scale model to canvas coordinates */
	private float		modelXMin, modelYMin, modelYMax, modelScale;

	/**
	 * Draws metro map on the canvas.
	 * @param canvas PlayN canvas to draw upon.
	 * @param map Metro map to draw on canvas.
	 */
	public void drawMetroMap(Canvas canvas, MetroMap map) {
		updateModelScaling(canvas, map);
		
		for (MetroLine line : map.lines) {
			drawMetroLine(canvas, map, line);
		}

		for (MetroStation station : map.stations) {
			drawMetroStation(canvas, station);
		}
	}

	private void updateModelScaling(Canvas canvas, MetroMap map) {
		// Calculate bounding box of model
		float modelXMax;
		modelXMin = Float.POSITIVE_INFINITY;
		modelXMax = Float.NEGATIVE_INFINITY;
		modelYMin = Float.POSITIVE_INFINITY;
		modelYMax = Float.NEGATIVE_INFINITY;
		for (MetroStation m : map.stations) {
			modelXMin = Math.min(modelXMin, m.x);
			modelXMax = Math.max(modelXMax, m.x);
			modelYMin = Math.min(modelYMin, m.y);
			modelYMax = Math.max(modelYMax, m.y);
		}

		// Add padding
		float padding = 0.02f;
		modelXMin -= padding * (modelXMax - modelXMin);
		modelXMax += padding * (modelXMax - modelXMin);
		modelYMin -= padding * (modelYMax - modelYMin);
		modelYMax += padding * (modelYMax - modelYMin);

		float modelWidth = modelXMax - modelXMin;
		float modelHeight = modelYMax - modelYMin;

		// Derive scale such that model fits on screen and aspect ratio is
		// maintained.
		float w = canvas.width();
		float h = canvas.height();
		float modelScaleW = w / modelWidth;
		float modelScaleH = h / modelHeight;
		modelScale = Math.min(modelScaleW, modelScaleH);
	}

	private float getCanvasX(float modelX) {
		return (modelX - modelXMin) * modelScale;
	}

	private float getCanvasY(float modelY) {
		boolean mirrorVertical = true;
		if (mirrorVertical)
			return (modelYMax - modelY) * modelScale;
		else
			return (modelY - modelYMin) * modelScale;
	}

	/**
	 * Draws metro line as a series of quadratic Bézier curves.
	 * @param canvas Canvas to draw upon.
	 * @param map MetroMap which must contain line.
	 * @param line MetroLine to draw on canvas.
	 */
	private void drawMetroLine(Canvas canvas, MetroMap map, MetroLine line) {
		Path path = canvas.createPath();
		for (int i = 1; i < line.stations.length; i++) {
			MetroStation m1 = line.stations[i - 1];
			MetroStation m2 = line.stations[i];
			MetroEdge e = map.getEdge(m1, m2);
			if (e != null) {
				// Draw connection
				if (i == 1)
					path.moveTo(getCanvasX(m1.x), getCanvasY(m1.y));

				canvas.setFillColor(Colors.colors[line.id]);
				canvas.fillRect(getCanvasX(m1.bsX), getCanvasY(m1.bsY), 3.0f, 3.0f);
				canvas.fillRect(getCanvasX(m2.bsX), getCanvasY(m2.bsY), 3.0f, 3.0f);
				
				// Bézier curve
				path.quadraticCurveTo(getCanvasX(e.cpX), getCanvasY(e.cpY), getCanvasX(m2.x), getCanvasY(m2.y));
			}
		}
		canvas.setStrokeColor(Colors.colors[line.id]);
		canvas.setStrokeWidth(4);
		canvas.strokePath(path);
		
		// Draw control points
//		for (int i = 1; i < line.stations.length; i++) {
//			MetroStation m1 = line.stations[i - 1];
//			MetroStation m2 = line.stations[i];
//			MetroEdge e = map.getEdge(m1, m2);
//			if (e != null) {
//				drawControlPoint(canvas, e);
//			}
//		}
	}

	@SuppressWarnings("unused")
	private void drawControlPoint(Canvas canvas, MetroEdge e) {
		canvas.setStrokeColor(0xFFFF0000);
		canvas.drawPoint(getCanvasX(e.cpX), getCanvasY(e.cpY));
	}

	private void drawMetroStation(Canvas canvas, MetroStation m) {
		canvas.setFillColor(0xFF000000);
		canvas.fillCircle(getCanvasX(m.x), getCanvasY(m.y), stationRadius);
	}
}
